Skip to content

feat: limitador plugin - #178

Draft
maleck13 wants to merge 5 commits into
praxis-proxy:mainfrom
maleck13:proposal/limitador-plugin
Draft

maleck13 wants to merge 5 commits into
praxis-proxy:mainfrom
maleck13:proposal/limitador-plugin

Conversation

@maleck13

@maleck13 maleck13 commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #152
Draft proposal plus POC code to prove out some of the assumptions. Introduces Limitador a plugin to PPE defines a rate limit plugin that can then be invoked. Backend storage and TokenRateLimiting not included currently

engine_settings:
  dispatch: policy

plugins:
  - name: global-ratelimit
    kind: ratelimit/limitador
    hooks: [http.request]
    mode: sequential
    capabilities: [read_headers]
    config:
      namespace: gateway-global-demo
      bindings:
        demo_user: http.request_headers.x-demo-user
        http_method: http.method
      limits:
        - max: 5
          seconds: 300
          conditions: ["demo_user == 'alice'", "http_method == 'GET'"]

  - name: toys-ratelimit
    kind: ratelimit/limitador
    hooks: [http.request]
    mode: sequential
    capabilities: [read_headers]
    config:
      namespace: gateway-toys-demo
      bindings:
        demo_user: http.request_headers.x-demo-user
        http_method: http.method
      limits:
        - max: 2
          seconds: 300
          conditions: ["demo_user == 'bob'", "http_method == 'GET'"]

global:
  authorization:
    pre_invocation:
      - "run(global-ratelimit)"

routes:
  - http: /toys
    authorization:
      pre_invocation:
        - "run(toys-ratelimit)"
  - http:
      path_prefix: /

Highly exprimental at the moment. But shows the intended direction

@coderabbitai

This comment was marked as resolved.

@maleck13

maleck13 commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

@didierofrivia interested in your thoughts on this proposal

@maleck13

maleck13 commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

@araujof can move out of draft but wanted your throughts on the POC implementation and whether it is close to how we would actually want to do this?

@araujof araujof changed the title Proposal/limitador plugin docs(proposal): limitador plugin Oct 7, 2026
@araujof araujof changed the title docs(proposal): limitador plugin feat: limitador plugin Oct 7, 2026
@araujof araujof added the enhancement New feature or request label Oct 7, 2026
@araujof araujof added this to the 0.5.0 milestone Oct 7, 2026
@maleck13

maleck13 commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Note this does not currently cover storage in external store

@maleck13
maleck13 force-pushed the proposal/limitador-plugin branch from 2c6518a to cb3faf6 Compare October 8, 2026 12:49
@araujof

araujof commented Oct 12, 2026

Copy link
Copy Markdown
Member

Thanks for putting this together, @maleck13. Running Limitador in process looks promising, especially if it removes a service hop from each check. I have two comments on the implementation and a broader suggestion for the plugin design.

Implementation

  • APL can run two plugin steps in parallel: and select a denial by branch order, while the single denial_violation slot holds whichever plugin wrote last. If A's denial wins but B wrote last, A's status and details can be lost, or B's details can be attached when their code and reason match. Could the full violation travel with the selected decision?
  • A global run(limiter) step is inherited by tool: and llm: routes, which dispatch CMF hooks, while this plugin only handles http.request. The load-time rejection makes that mismatch explicit. The documented two-filter setup seems reasonable for this PR: the first filter resolves identity and runs http.request once; the second handles entity policy. That still needs a real gateway check. A later single-filter design would need an HTTP admission stage after identity resolution and before CMF dispatch, outside the steps inherited by entity routes.

Direction

We already have an experimental quota/limitador plugin that calls an external Limitador service. I'd aim for one public Limitador plugin kind with request and token metering modes, and embedded and service backends. The two modes can keep separate handlers. Here is how the implementations work today:

quota/limitador ratelimit/limitador
Counts LLM tokens used HTTP requests
Hooks Check at cmf.llm_input, debit at cmf.llm_output Check and increment at http.request
Limit definitions Configured in the external Limitador service Configured in PPE plugin config
Counter storage Shared through the service In memory per instance; Valkey planned

I'd define a typed PPE limit extension for both request counts and token quotas. Static rules can be compiled once, then the applicable limits (or references to them) passed for each invocation. The plugin also needs the capability-filtered identity and auth data, plus HTTP request or LLM usage data at the relevant hook. I'd add a test where a verified identity attribute selects a limit, then check that the request or token charge is correct. Limitador v0.13 mainly takes string-valued inputs, so we need to define how those typed values become Limitador inputs.

The quota plugin already has a private QuotaBackend check/report seam and handles missing usage and failed debits. We could build an embedded token mode from that code, with shared counters across replicas.

From PPE's perspective, we probably don't want the embedded Redis backend to depend on Lua scripts. Limitador v0.13 accepts a Redis connection manager, but uses redis 0.27 while PPE uses 1.7, so I'd align those APIs first. Limitador's scripts handle counter TTLs, batch updates, and atomic reservations across limits. Removing them would need a new storage design and expiry and concurrency tests. A Limitador fork seems like a reasonable place to try it.

cc: @shaneutt @alexsnaps @terylt

@terylt

terylt commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

I'm just curious, Are we going to want to rate limit on things like:

  1. agent id
  2. tool/resource/prompt
  3. agent id -> tool/resource/prompt combination
  4. agent id -> agent id combination
  5. user id?

Should the counter keys be configurable and composable? I think HTTP will be valuable but I could see situations where semantic rate limiting could be useful.

@maleck13

maleck13 commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

I agree a fork is a reasonable way to go right now and aligning the client versions makes sense. However on the Lua scripts, I would defer to @alexsnaps as it is not clear what we would be losing here or what the viable alternative would be.
https://github.com/Kuadrant/limitador/blob/main/limitador/src/storage/redis/scripts.rs .
Note these scripts would be executed in the redis server and not via PPE

@araujof araujof modified the milestones: 0.5.0, 0.6.0 Oct 12, 2026
Signed-off-by: craig <cbrookes@redhat.com>
…es and be tested through a real gateway

Signed-off-by: craig <cbrookes@redhat.com>
Signed-off-by: craig <cbrookes@redhat.com>
…will work with limitador

Define follow on work and limitations of this first experimental plugin

Signed-off-by: craig <cbrookes@redhat.com>
@maleck13
maleck13 force-pushed the proposal/limitador-plugin branch from 9950272 to e4a7ff3 Compare October 16, 2026 14:05
@maleck13

Copy link
Copy Markdown
Collaborator Author

possibly relates to this enhancement praxis-proxy/enhancements#25

@eguzki

eguzki commented Oct 20, 2026

Copy link
Copy Markdown

@araujof Just to confirm that I understood your suggestion on the plugin design. You are advocating one plugin two orthogonal config dimensions:

plugins:
  - name: my-limiter
    kind: limitador        # ONE public kind
    config:
      mode: token          # request | token   ← what you meter
      backend: service     # embedded | service ← where counters live
      ...

Is that right?

One thing to pin down in backend:: "embedded vs service" conflates two independent axes.

  • in-process crate vs. remote service
  • Counter storage: in process memory (per-replica, lost on restart) or in external Redis/Valkey (shared across replicas)

What were your thoughts on this backend ?

@maleck13

Copy link
Copy Markdown
Collaborator Author

@araujof Just to confirm that I understood your suggestion on the plugin design. You are advocating one plugin two orthogonal config dimensions:

plugins:
  - name: my-limiter
    kind: limitador        # ONE public kind
    config:
      mode: token          # request | token   ← what you meter
      backend: service     # embedded | service ← where counters live
      ...

Is that right?

One thing to pin down in backend:: "embedded vs service" conflates two independent axes.

  • in-process crate vs. remote service
  • Counter storage: in process memory (per-replica, lost on restart) or in external Redis/Valkey (shared across replicas)

What were your thoughts on this backend ?

Once we have redis support in the plugin, I wonder is the backend service type even needed. I would like to think it isn't but perhaps I am missing the use case?
@hexfusion @asaadbalum

@araujof

araujof commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

@araujof Just to confirm that I understood your suggestion on the plugin design. You are advocating one plugin two orthogonal config dimensions:
...
What were your thoughts on this backend ?

@eguzki Yes, one public Limitador kind is the direction I had in mind.

I meant backend to describe where Limitador runs: embedded in PPE or as a separate service. Counter storage is a further choice for the embedded case: local memory for this PoC, with Redis/Valkey as possible shared storage later. A service would own its storage configuration. So backend: { type: embedded, storage: memory } expresses the current proposal, without implying that every combination is implemented.

@maleck13 I agree that embedded Limitador with shared storage could make a separate service unnecessary for a PPE-only deployment. Redis support alone would not make it a drop-in replacement for the existing quota/limitador plugin, though: that plugin uses service-owned limit definitions, and we still need token behavior and migration parity. I'd keep it available during the experimental phase, then decide whether a service option in the unified kind serves a concrete need, such as centrally managed limits or other clients sharing Limitador. I wouldn't make that option a prerequisite for this PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

Spike using Limitador crate as plugin for application rate limiting

4 participants